﻿using System;
using System.Collections.Generic;

using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Diagnostics;
using SharpDX.Direct3D;
using SharpDX.Direct3D10;
using SharpDX.DXGI;
using SharpDX.Windows;

using Device = SharpDX.Direct3D10.Device;
using Buffer = SharpDX.Direct3D10.Buffer;
using Resource = SharpDX.Direct3D10.Resource;
using DX11Engine.Diagnostics;

namespace DX11Engine
{


    public class SurfaceRenderData
    {
        public ShaderBytecode EffectShaderBCode { get; set; }
        public Effect EffectHandle { get; set; }

        public EffectTechnique Technique { get; set; }
        public EffectPass Pass { get; set; }

        public void Dispose()
        {
            if (EffectShaderBCode != null) { EffectShaderBCode.Dispose(); }
            if (EffectHandle != null) { EffectHandle.Dispose(); }

            if (Technique != null) { Technique.Dispose(); }
            if (Pass != null) { Pass.Dispose(); }
        }

        public ShaderBytecode PassSignature { get; set; }
    }


    public class JEntity
    {
        public Vector3 Position { get; set; }
        public Vector3 Rotation { get; set; }
        public Vector3 Scale { get; set; }

        public bool IsVisible { get; set; }
        public bool IsCulled { get; set; }
        public string Name { get; set; }

        private Vector3 emptyVector = new Vector3(0, 0, 0);

        public void JEntityConstructor(string Name = null)
        {
            this.IsCulled = false;
            this.IsVisible = true;
            this.Name = (String.IsNullOrEmpty(Name) ? "Object0" : Name);
        }

        public void Move(float X, float Y, float Z)
        {
            this.Position += Vector3.TransformCoordinate(emptyVector, Matrix.Translation(new Vector3(X, Y, Z)) *
                Matrix.RotationYawPitchRoll(Rotation.Y, -Rotation.X, Rotation.Z));
        }

        public void Translate(float X, float Y, float Z)
        { 
            this.Position += Vector3.TransformCoordinate(emptyVector, 
                Matrix.Translation(new Vector3(X, Y, Z)));
        }

        public void Rotate(float Pitch, float Yaw, float Roll)
        {
            this.Rotation = new Vector3(
                Rotation.X + Pitch - (Rotation.X + Pitch > 360 ? 360 : 0),
                Rotation.Y + Yaw - (Rotation.Y + Yaw > 360 ? 360 : 0),
                Rotation.Z + Roll - (Rotation.Z + Roll > 360 ? 360 : 0));
        }

        public void SetPosition(float X, float Y, float Z)
        { this.Position = new Vector3(X, Y, Z); }

        public void SetRotation(float Pitch, float Yaw, float Roll)
        { this.Rotation = new Vector3(Pitch, Yaw, Roll); }

        public void SetScale(float X, float Y, float Z)
        { this.Scale = new Vector3(X, Y, Z); }
    }

    public enum LayerType
    { 
        Texture = 0,
        LightMap = 1,
        Alpha = 2,
        BumpMap = 3,
        NormalMap = 4,
        Additive = 5,
        Multiply = 6
    }

    public class TextureLayer
    {
        public string FileName { get; set; }
        public LayerType Type { get; set; }
        public bool ContainsAlphaInfo { get; set; }
        public TextureLayer(string fileName = "", LayerType type = LayerType.Texture)
        { this.FileName = fileName; this.Type = type; }
    }

    public class MeshMaterial
    {
        public string Name { get; set; }
        public float Ambient { get; set; }
        public float Diffuse { get; set; }
        public float Specular { get; set; }
        public float Power { get; set; }
        public float Emission { get; set; }
        public TextureLayer[] TextLayers { get; set; }
        public ShaderResourceView[] TextResources { get; set; }
        public MeshMaterial(string Name = "Default0") { this.Name = Name; this.TextResources = new ShaderResourceView[6]; }
        public void AddTextureLayer(string fileName, LayerType type)
        {
            bool AddedTextureLayer = false;
            for (int x = 0; x < this.TextLayers.Length; x++) {
                if (this.TextLayers[x] == null) { 
                    this.TextLayers[x] = new TextureLayer(fileName, type); 
                    AddedTextureLayer = true; 
                }
            }

            if (!AddedTextureLayer)
            {
                if (this.TextLayers.Length < 6)
                {
                    TextureLayer[] referc = this.TextLayers;
                    Array.Resize(ref referc, this.TextLayers.Length + 1);
                    this.TextLayers = referc;
                    this.TextLayers[this.TextLayers.Length - 1] = new TextureLayer(fileName, type);
                }
                else { throw new Exception("Only a maximun of 6 texture layers are allowed!"); }
            }


            //TextureLayer[] tmpLyrs = this.TextLayers;
            //if (TextLayers.Length < (int)type)
            //{
            //    tmpLyrs = new TextureLayer[(int)type + 1];
            //    this.TextLayers.CopyTo(tmpLyrs, 0);
            //    this.TextLayers = tmpLyrs;
            //}
            //this.TextLayers[(int)type].Type = 
        }

        public float[] RGBA { get; set; }
    }

    public struct MeshData
    {
        public PositionNormalTextured[] VertexData { get; set; }
        public MeshMaterial MeshMaterial { get; set; }
        public bool InternalsInstantiated;
        public Buffer InternalBufferHandle;
        public EffectShaderResourceVariable EffectTextures;
        public ShaderResourceView shdrRsrcVew;
        public EffectPass EffectPass;
        public ShaderSignature Signature;
        public EffectTechnique Tech;
    }

    public class J3DObject : JEntity
    {
        private Buffer InternalBuffer;

        public MeshMaterial[] Materials { get; set; }
        public MeshData[] ModelData { get; set; }

        private InputLayout VertexDataLayout;
        public InputElement[] LayoutInputElements { get; set; }
        public int StrideSize { get; private set; }
        
        // PrimitiveType 
        public PrimitiveTopology VertixListPrimitiveType { get; set; }

        public SurfaceRenderData SurfaceData { get; set; }
        //public SurfaceLayer[] Material { get; set; }

        public void DrawEntity(Device device, RenderingDiagnostic rndrDiag)
        {
            if (ModelData != null)
            {
                rndrDiag.AddToStatistic("EntitySubPartsToRender", ModelData.Length);
                for (int x = 0; x < ModelData.Length; x++)
                {
                    if (ModelData[x].VertexData.Length > 0)
                    {
                        rndrDiag.BeginSpecific("--RenderingMeshSubPart");

                        MeshMaterial Mat = ModelData[x].MeshMaterial;

                        // Cycle through textures and ensure they are loaded: (Avg 6 ticks)
                        if (Mat != null && Mat.TextLayers != null)
                        {
                            for (int indx = 0; indx < Mat.TextLayers.Length; indx++)
                            {
                                if (Mat.TextLayers[indx] != null && Mat.TextResources[(int)Mat.TextLayers[indx].Type] == null)
                                { Mat.TextResources[(int)Mat.TextLayers[indx].Type] = ShaderResourceView.FromFile(device, Mat.TextLayers[indx].FileName); }
                            }
                        }

                        // Instantiate Vertex buffer from vertex data
                        if (ModelData[x].InternalBufferHandle == null)
                        {
                            ModelData[x].InternalBufferHandle = Buffer.Create(
                                device, BindFlags.VertexBuffer, ModelData[x].VertexData);
                        }

                        // Set the correct effect: (Avg 35 ticks)
                        if (ModelData[x].EffectPass == null)
                        {
                            if (Mat != null && Mat.TextResources != null)
                            {
                                if (Mat.TextResources[0] != null)
                                {
                                    if (Mat.TextResources[1] != null)
                                    {
                                        if (Mat.TextResources[4] != null)
                                        { ModelData[x].Tech = SurfaceData.Technique = SurfaceData.EffectHandle.GetTechniqueByName("NormalLightMapTextured"); }
                                        else { ModelData[x].Tech = SurfaceData.Technique = SurfaceData.EffectHandle.GetTechniqueByName("LightMapTextured"); }
                                    }
                                    else { ModelData[x].Tech = SurfaceData.EffectHandle.GetTechniqueByName("VertexColoredTextured"); }
                                }
                                else
                                { if (Mat.TextResources[1] != null) { ModelData[x].Tech = SurfaceData.EffectHandle.GetTechniqueByName("LightMapVertexColored"); } }
                            }

                            if (ModelData[x].Tech == null) { ModelData[x].Tech = SurfaceData.EffectHandle.GetTechniqueByName("VertexColored"); }
                            ModelData[x].EffectPass = ModelData[x].Tech.GetPassByIndex(0);
                        }

                        // Get shader texture handle and set resources if need be: (Avg 35 ticks)
                        if (Mat != null && Mat.TextResources != null)
                        {
                            ModelData[x].EffectTextures = SurfaceData.EffectHandle.GetVariableByName("tex2D").AsShaderResource();
                            ModelData[x].EffectTextures.SetResourceArray(Mat.TextResources);
                        }

                        // Setup effect technique & pass info: (Avg 15 ticks)
                        SurfaceData.Technique = ModelData[x].Tech;
                        SurfaceData.Pass = ModelData[x].EffectPass;
                        SurfaceData.PassSignature = ModelData[x].EffectPass.Description.Signature;

                        // Set Internal buffer:  (Avg 2 ticks)
                        InternalBuffer = ModelData[x].InternalBufferHandle;

                        // Set input layout: (Avg 2 ticks)
                        if (VertexDataLayout == null)
                        {
                            if (LayoutInputElements.Length > 0)
                            { VertexDataLayout = new InputLayout(device, SurfaceData.PassSignature, LayoutInputElements); }
                            else { throw new Exception("No Vertex Data Layout Provided!"); }
                        }

                        // Prepare All the stages (Avg. 50 ticks)
                        device.InputAssembler.InputLayout = VertexDataLayout;
                        device.InputAssembler.PrimitiveTopology = VertixListPrimitiveType;
                        device.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(InternalBuffer, StrideSize, 0));

                        rndrDiag.BeginSpecific("---DrawMeshSubPart");
                        for (int i = 0; i < SurfaceData.Technique.Description.PassCount; ++i)
                        {
                            SurfaceData.Pass.Apply();
                            device.Draw(ModelData[x].VertexData.Length, 0);
                        }
                        rndrDiag.EndSpecific("---DrawMeshSubPart");

                        rndrDiag.EndSpecific("--RenderingMeshSubPart");
                    }
                }
            }
                
        }

        public J3DObject(Device device, string Name = null)
        {
            JEntityConstructor(Name);
            this.StrideSize = Utilities.SizeOf<PositionNormalTextured>();
            this.Scale = new Vector3(1, 1, 1);
            this.VertixListPrimitiveType =  PrimitiveTopology.TriangleList;

            SurfaceData = new SurfaceRenderData();
            SurfaceData.EffectShaderBCode = ShaderBytecode.CompileFromFile("MiniTri.fx", "VS", "fx_4_0", ShaderFlags.None, EffectFlags.None);
            SurfaceData.EffectHandle = new Effect(device, SurfaceData.EffectShaderBCode);
            

            Materials = new MeshMaterial[1];
        }

        public void Dispose()
        {
            //if (shdrRsrcVew != null) { shdrRsrcVew.Dispose(); }
            if (SurfaceData != null) { SurfaceData.Dispose(); }
            if (InternalBuffer != null) { InternalBuffer.Dispose(); }
            if (VertexDataLayout != null) { VertexDataLayout.Dispose(); }
        }
    }

    public class CameraViewport : JEntity
    {
        public Viewport Viewport { get; set; }
        public Vector3 TargetPosition { get; set; }
        public float FieldOfView { get; set; }
        public float AspectRatio { get; set; }
        public float NearPlaneClip { get; set; }
        public float FarPlaneClip { get; set; }
        public string ViewportName { get; set; }
        private Vector3 UpVector = Vector3.UnitY; // Define which way is "up"

        public CameraViewport(int Width, int Height)
        {
            FieldOfView = (float)Math.PI / 4.0f;
            AspectRatio = (float)Width / (float)Height;
            NearPlaneClip = 0.0f;
            FarPlaneClip = 1.0f;
            Viewport = new Viewport(0, 0, Width, Height, NearPlaneClip, FarPlaneClip);
        }

        public Vector3 CameraTarget
        {
            get
            {
                // Calculate view and transformation matrixes:
                Vector3 cameraTarget = Position;
                cameraTarget.X += ((float)Math.Sin(Rotation.Y) * 10.0f); //Set target 10 units ahead
                cameraTarget.Y += ((float)Math.Tan(Rotation.X) * 10.0f);
                cameraTarget.Z += ((float)Math.Cos(Rotation.Y) * 10.0f);
                return cameraTarget;
            }
        }

        public Matrix ViewMatrix
        { get { return Matrix.LookAtLH(Position, CameraTarget, UpVector); } }

        public Matrix ProjectionMatrix
        { get { return Matrix.PerspectiveFovLH(FieldOfView, AspectRatio, 0.1f, 100.0f); } }
    }
}
